{$MODE objfpc}
{$H+}

unit SimplePython;

interface
  uses PythonTypes, PythonObject, PythonAbstract, 
    PythonStringObject, PythonMethodObject, PythonTupleObject, PythonClassObject,
    PythonIntObject, PythonRun, PythonImport;
  
  type
    ComparisonType = (
      LessThan = 0,
      LessThanEqual = 1,
      Equal = 2,
      NotEqual = 3,
      GreaterThan = 4,
      GreaterThanOrEqual = 5
    );
    
    TPythonTuple = class;
    TPythonFunction = class;
    TPythonMethod = class;
    TPythonString = class;
    TPythonUnicode = class;
    TPythonDictionary = class;
    
    TPythonEngine = class
      public
        class procedure Init();
        class function Instance(): TPythonEngine;
        
        destructor Destroy(); override;
        procedure RunSimple(code: String);
        
      private
        constructor Create();
    end;
        
    TPythonObject = class
      private
        _Data: PyObjectPtr;  //The pointer to the data for this object...
        
        function GetRefCnt(): Integer;
      public
        constructor Create(dat: PyObjectPtr);
        constructor FromInt(value: Long);
        constructor FromString(str: String);
        destructor Destroy(); override;
        
        property ReferenceCount: Integer read GetRefCnt;
        
        function Call(): TPythonObject; overload;
        function Call(args: TPythonTuple): TPythonObject; overload;
        function Call(args: TPythonTuple; kw: TPythonDictionary): TPythonObject; overload;
        
        function  CallMethod(name: String; args: TPythonTuple): TPythonObject; overload;
        function  CallMethod(name: String): TPythonObject; overload;

        procedure CallProcedure(proc: String); overload;
        procedure CallProcedure(proc: String; args: TPythonTuple); overload;
        
        function  GetAttr(name: String): TPythonObject; overload;
        function  GetAttr(name: TPythonObject): TPythonObject; overload;
        
        procedure SetAttr(name: String; v: TPythonObject); overload;
        procedure SetAttr(name: TPythonObject; v: TPythonObject); overload;
          
        procedure DeleteAttr(name: String); overload;
        procedure DeleteAttr(name: TPythonObject); overload;
          
        function RichCompare(ob: TPythonObject; compType: ComparisonType): TPythonObject;
        function RichCompareBool(ob: TPythonObject; compType: ComparisonType): Boolean;
        
        function Cmp(ob: TPythonObject): Integer;
        function Repr(): TPythonString;
        function Str(): TPythonString;
        function Unicode(): TPythonUnicode;
        
        function IsInstance(cls: TPythonObject): Boolean;
        function IsSubclass(cls: TPythonObject): Boolean;
        
        function IsCallable(): Boolean;
        
        function AsTuple(): TPythonTuple;
        function AsFunction(): TPythonFunction;
        function AsMethod(): TPythonMethod;

        function ToTuple(): TPythonTuple;
        function ToFunction(): TPythonFunction;
        function ToMethod(): TPythonMethod;
        
        function ToPyString(): TPythonObject;
        function ToString(): String;

        function ReadString(): String;
    end;
    
    TPythonModule = class (TPythonObject)
      public
        constructor Create(dat: PyObjectPtr);
        constructor Create(dat: TPythonObject);
        destructor Destroy(); override;
                  
        class function LoadModule(module: String): TPythonModule;
    end;

    TPythonString = class (TPythonObject)
      public
        constructor Create(dat: PyObjectPtr); overload;
        constructor Create(dat: TPythonObject); overload;
        destructor Destroy(); override;
    end;

    TPythonUnicode = class (TPythonObject)
      public
        constructor Create(dat: PyObjectPtr); overload;
        constructor Create(dat: TPythonObject); overload;
        destructor Destroy(); override;
    end;

    TPythonDictionary = class (TPythonObject)
      public
        constructor Create(dat: PyObjectPtr); overload;
        constructor Create(dat: TPythonObject); overload;
        destructor Destroy(); override;
    end;
    
    TPythonTuple = class (TPythonObject)
      public
        constructor Create(dat: PyObjectPtr);
        constructor Create(dat: TPythonObject);
        constructor Create(count: Integer);
        constructor Create(args: Array of Const);
        
        destructor Destroy(); override;
        
        procedure SetItem(idx: Integer; ob: TPythonObject);
    end;
    
    TPythonMethod = class (TPythonObject)
      public
        constructor Create(dat: PyObjectPtr);
        constructor Create(dat: TPythonObject);
        destructor Destroy(); override;
                
        function Call(args: TPythonTuple): TPythonObject; overload;
        function Call(): TPythonObject; overload;
    end;
    
    TPythonFunction = class (TPythonObject)
      public
        constructor Create(dat: PyObjectPtr);
        constructor Create(dat: TPythonObject);
        destructor Destroy(); override;
    end;
    
implementation
  uses SysUtils, Classes;
  
  var _Instance: TPythonEngine = nil;

// =================
// = TPythonEngine =
// =================
class procedure TPythonEngine.Init();
begin
  if _Instance = nil then _Instance := TPythonEngine.Create();
end;

constructor TPythonEngine.Create();
begin
  Py_Initialize();
end;

destructor TPythonEngine.Destroy();
begin
  inherited;
  
  Py_Finalize();
  _Instance := nil;
end;

class function TPythonEngine.Instance(): TPythonEngine;
begin
  if _Instance = nil then Init();
  result := _Instance;
end;

procedure TPythonEngine.RunSimple(code: String);
begin
  if PyRun_SimpleStringFlags(PChar(code), nil) = -1 then
  begin
    raise Exception.Create('Error with simple script: ' + code);
  end;
end;

// =================
// = TPythonObject =
// =================

constructor TPythonObject.Create(dat: PyObjectPtr);
begin
  _Data := dat;
  if dat <> nil then Py_INCREF(dat);
end;

constructor TPythonObject.FromInt(value: Long);
begin
  _Data := PyInt_FromLong(value);
end;

constructor TPythonObject.FromString(str: String);
begin
  _Data := PyString_FromString(PChar(str));
end;

destructor TPythonObject.Destroy();
begin
  inherited;
  
  //WriteLn('Disposing TPythonObject - ', ReferenceCount);
  if _Data <> nil then Py_DECREF(_Data);
  _Data := nil;
end;

function TPythonObject.GetAttr(name: String): TPythonObject; overload;
var
  pAttr: PyObjectPtr;
begin
  pAttr := PyObject_GetAttrString(_Data, PChar(name)); //new reference
  result := TPythonObject.Create(pAttr);
  Py_DECREF(pAttr);
end;

procedure TPythonObject.SetAttr(name: String; v: TPythonObject); overload;
begin
  if PyObject_SetAttrString(_Data, PChar(name), v._Data) = -1 then
  begin
    raise Exception.Create('Failed to set attribute ' + name);
  end;
end;

function TPythonObject.GetAttr(name: TPythonObject): TPythonObject; overload;
var
  pAttr: PyObjectPtr;
begin
  pAttr := PyObject_GetAttr(_Data, name._Data); //new reference
  result := TPythonObject.Create(pAttr);
  Py_DECREF(pAttr);
end;

procedure TPythonObject.SetAttr(name: TPythonObject; v: TPythonObject); overload;
begin
  if PyObject_SetAttr(_Data, name._Data, v._Data) = -1 then
  begin
    raise Exception.Create('Failed to set attribute');
  end;
end;

procedure TPythonObject.DeleteAttr(name: String); overload;
begin
  if PyObject_DelAttrString(_Data, PChar(name)) = -1 then
  begin
    raise Exception.Create('Failed to delete attribute ' + name);
  end;
end;

procedure TPythonObject.DeleteAttr(name: TPythonObject); overload;
begin
  if PyObject_DelAttr(_Data, name._Data) = -1 then
  begin
    raise Exception.Create('Failed to delete attribute');
  end;
end;

function TPythonObject.AsTuple(): TPythonTuple;
begin
  result := TPythonTuple.Create(self);
  Free();
end;

function TPythonObject.AsFunction(): TPythonFunction;
begin
  result := TPythonFunction.Create(self);
  Free();
end;

function TPythonObject.AsMethod(): TPythonMethod;
begin
  result := TPythonMethod.Create(self);
  Free();
end;

function TPythonObject.ToTuple(): TPythonTuple;
begin
  result := TPythonTuple.Create(self);
end;

function TPythonObject.ToFunction(): TPythonFunction;
begin
  result := TPythonFunction.Create(self);
end;

function TPythonObject.ToMethod(): TPythonMethod;
begin
  result := TPythonMethod.Create(self);
end;

function TPythonObject.ToPyString(): TPythonObject;
var
  temp: PyObjectPtr;
begin
  temp := PyObject_Str(_Data);
  //WriteLn('ToPyString - ref ', temp^.ob_refcnt);
  result := TPythonObject.Create(temp);
  Py_DECREF(temp);
  //WriteLn('End ToPyString - ref ', temp^.ob_refcnt);
end;

function TPythonObject.ToString(): String;
var
  temp: TPythonObject;
begin
  temp := self.ToPyString();
  result := PyString_AsString(temp._Data);
  temp.Free();
end;

function TPythonObject.ReadString(): String;
begin
  if PyString_Check(_Data) then
    result := PyString_AsString(_Data)
  else
    raise Exception.Create('TPythonObject is not a PyString');
end;

function TPythonObject.Call(args: TPythonTuple): TPythonObject; overload;
begin
  if Assigned(args) then
    result := TPythonObject.Create(PyObject_CallObject(_Data, args._Data))
  else
    result := TPythonObject.Create(PyObject_CallObject(_Data, nil));
end;

function TPythonObject.Call(): TPythonObject; overload;
begin
  result := Call(nil);
end;

function TPythonObject.CallMethod(name: String; args: TPythonTuple): TPythonObject; overload;
var
  meth: TPythonFunction;
begin
  meth := GetAttr(name).AsFunction();
  result := meth.Call(args);
	meth.Free();
end;

function TPythonObject.CallMethod(name: String): TPythonObject; overload;
begin
   result := TPythonObject.Create( PyObject_CallMethod(_Data, PChar(name), PChar(nil), []));
end;

procedure TPythonObject.CallProcedure(proc: String; args: TPythonTuple); overload;
var
  meth: TPythonFunction;
  res: TPythonObject;
begin
  meth := GetAttr(proc).AsFunction();
  res := meth.Call(args);
  res.Free();
	meth.Free();
end;

procedure TPythonObject.CallProcedure(proc: String); overload;
begin
  CallProcedure(proc, nil);
end;

function TPythonObject.GetRefCnt(): Integer;
begin
  result := _Data^.ob_refcnt;
end;

function TPythonObject.RichCompare(ob: TPythonObject; compType: ComparisonType): TPythonObject;
var
  ref: PyObjectPtr;
begin
  ref := PyObject_RichCompare(_Data, ob._Data, Integer(compType));
  result := TPythonObject.Create(ref);
  Py_DecRef(ref);
end;

function TPythonObject.RichCompareBool(ob: TPythonObject; compType: ComparisonType): Boolean;
begin
  case PyObject_RichCompareBool(_Data, ob._Data, Integer(compType)) of
    0: result := false;
    1: result := true;
    else raise Exception.Create('Error in RichCompareBool.');
  end;
end;

function TPythonObject.Cmp(ob: TPythonObject): Integer;
begin
  if PyObject_Cmp(_Data, ob._Data, result) = -1 then
  begin
    raise Exception.Create('Error during Cmp');
  end;
end;

function TPythonObject.Repr(): TPythonString;
var
  ref: PyObjectPtr;
begin
  ref := PyObject_Repr(_Data);
  result := TPythonString.Create(ref);
  Py_DecRef(ref);
end;

function TPythonObject.Str(): TPythonString;
var
  ref: PyObjectPtr;
begin
  ref := PyObject_Str(_Data);
  result := TPythonString.Create(ref);
  Py_DecRef(ref);
end;

function TPythonObject.Unicode(): TPythonUnicode;
var
  ref: PyObjectPtr;
begin
  ref := PyObject_Unicode(_Data);
  result := TPythonUnicode(ref);
  Py_DecRef(ref);
end;

function TPythonObject.IsInstance(cls: TPythonObject): Boolean;
begin
  case PyObject_IsInstance(_Data, cls._Data) of
    0: result := false;
    1: result := true;
    else raise Exception.Create('Error in IsInstance.');
  end;
end;

function TPythonObject.IsSubclass(cls: TPythonObject): Boolean;
begin
  case PyObject_IsSublclass(_Data, cls._Data) of
    0: result := false;
    1: result := true;
    else raise Exception.Create('Error in IsInstance.');
  end;
end;

function TPythonObject.IsCallable(): Boolean;
begin
  result := PyCallable_Check(_Data) <> 0;
end;        

// =================
// = TPythonModule =
// =================
constructor TPythonModule.Create(dat: PyObjectPtr);
begin
  Inherited Create(dat);
end;

constructor TPythonModule.Create(dat: TPythonObject);
begin
  Inherited Create(dat._Data);
end;

destructor TPythonModule.Destroy();
begin
  Inherited;
end;

class function TPythonModule.LoadModule(module: String): TPythonModule;
var
  pName: PyObjectPtr;
  pModule: PyObjectPtr;
begin
  //Import module...
	pName := PyString_FromString(PChar(module));
	pModule := PyImport_Import(pName);
	
	{- $IFDEF DEBUG}
  WriteLn('Imported Module : ', module, ' : ', HexStr(pModule));
  {- $ENDIF}
  
	result := TPythonModule.Create(pModule);
	
	Py_DECREF(pName);
	Py_DECREF(pModule);
end;

// =================
// = TPythonString =
// =================

constructor TPythonString.Create(dat: PyObjectPtr); overload;
begin
  inherited Create(dat);
end;

constructor TPythonString.Create(dat: TPythonObject); overload;
begin
  inherited Create(dat._Data);
end;

destructor TPythonString.Destroy();
begin
  inherited;
end;

// ==================
// = TPythonUnicode =
// ==================
constructor TPythonUnicode.Create(dat: PyObjectPtr); overload;
begin
  Inherited Create(dat);
end;

constructor TPythonUnicode.Create(dat: TPythonObject); overload;
begin
  Inherited Create(dat._Data);
end;

destructor TPythonUnicode.Destroy();
begin
  Inherited;
end;

// =====================
// = TPythonDictionary =
// =====================
constructor TPythonDictionary.Create(dat: PyObjectPtr); overload;
begin
  Inherited Create(dat);
end;

constructor TPythonDictionary.Create(dat: TPythonObject); overload;
begin
  Inherited Create(dat._Data);
end;

destructor TPythonDictionary.Destroy();
begin
  Inherited;
end;

// ================
// = TPythonTuple =
// ================
constructor TPythonTuple.Create(dat: PyObjectPtr);
begin
  Inherited Create(dat);
end;

constructor TPythonTuple.Create(dat: TPythonObject);
begin
  Inherited Create(dat._Data);
end;

constructor TPythonTuple.Create(count: Integer);
var
  temp: PyObjectPtr;
begin
  temp := PyTuple_New(count);
  Inherited Create(temp);
  Py_DECREF(temp);
end;       

constructor TPythonTuple.Create(args: Array of Const);
var
  i: Integer;
  temp: PyObjectPtr;
  val: TPythonObject;
begin
  temp := PyTuple_New(Length(args));
  
  Inherited Create(temp);
  
  Py_DECREF(temp);
  temp := nil;
  
  for i := Low(args) to High(args) do
  begin
    case args[i].vtype of
      vtInteger: val := TPythonObject.FromInt(args[i].vInteger);
    end;
    
    //Add the arg i (now in val) to self...
    SetItem(i, val);
    val.Free();
  end;  
end;

destructor TPythonTuple.Destroy();
begin
  inherited;
end;

procedure TPythonTuple.SetItem(idx: Integer; ob: TPythonObject); 
begin
  Py_IncRef(ob._Data); //to match the one stolen below...
  PyTuple_SetItem(_Data, idx, ob._Data);
  //WriteLn('SetItem[', idx, '] = ', ob.ToString(), ' == ', self.ToString());
end;

// ===================
// = TPythonFunction =
// ===================

constructor TPythonFunction.Create(dat: PyObjectPtr);
begin
  Inherited Create(dat);
end;

constructor TPythonFunction.Create(dat: TPythonObject);
begin
  Inherited Create(dat._Data);
end;

destructor TPythonFunction.Destroy();
begin
  Inherited;
end;

// =================
// = TPythonMethod =
// =================
constructor TPythonMethod.Create(dat: PyObjectPtr);
begin
  Inherited Create(dat);
end;

constructor TPythonMethod.Create(dat: TPythonObject);
begin
  Inherited Create(dat._Data);
end;

destructor TPythonMethod.Destroy();
begin
  Inherited;
end;

function TPythonMethod.Call(args: TPythonTuple): TPythonObject; overload;
var
  fnObj: PyObjectPtr;
begin
  fnObj := PyMethod_Function(_Data); //borrowed

  if Assigned(args) then
    result := TPythonObject.Create(PyObject_CallObject(fnObj, args._Data))
  else
    result := TPythonObject.Create(PyObject_CallObject(fnObj, nil));  
end;

function TPythonMethod.Call(): TPythonObject; overload;
begin
  result := Call(nil);
end;

finalization
  if _Instance <> nil then
  begin
    _Instance.Free;
    _Instance := nil;
  end;
end.